package com.google.code.b0rx0r.program;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.code.b0rx0r.ColorScheme;

public class Sequence extends AbstractTriggerable implements Serializable, Sequencable, Iterable<SequencedSequencable> {
	private static final long serialVersionUID = "Sequence 1.0".hashCode(); //$NON-NLS-1$

	private int trackCount = 4;

	private int lengthBars = 1;
	private int beatsPerBar = 4;
	private int snappointsPerBar = 8;
	
	private float tempoBPM;
	private boolean syncToHostTempo = true;

	private List<SequencedSequencable> sss = new ArrayList<SequencedSequencable>();
	private transient List<SequenceListener> listeners = new ArrayList<SequenceListener>();
	private transient ProgramManager programManager;
	
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		listeners = new ArrayList<SequenceListener>();
	}
	
	public Sequence(ProgramManager pm) {
		this.programManager = pm;
		setColor(ColorScheme.DEFAULT_SEQUENCE_COLOR);
		updateSnapPoints();
	}
	
	public int getTrackCount() {
		return trackCount;
	}
	
	public void addSequencable(Sequencable seq, int track, int offset) {
		sss.add(new SequencedSequencable(seq, track, offset));
	}

	@Override
	public Iterator<SequencedSequencable> iterator() {
		return sss.iterator();
	}

	@Override
	public void fireSnapPointMoved(SnapPoint snapPoint) {
		for (SequenceListener l : listeners) {
			l.snapPointsChanged();
		}
	}


	private Set<SnapPoint> snapPoints = new HashSet<SnapPoint>();
	
	public void updateSnapPoints() {
		snapPoints.clear();
		
		addBeatSnapPoints();
		
		fireSnapPointMoved(null);
	}
	
	private void addBeatSnapPoints() {
		for (int i=0; i<snappointsPerBar * lengthBars; i++) {
			SnapPoint sp = new RelativeSnapPoint(this, (int)(i * convertBeatsToSamples() * ((float) beatsPerBar / (float)snappointsPerBar)));
			snapPoints.add(sp);
		}
	}

	@Override
	public Iterable<SnapPoint> getRelativeSnapPoints() {
		return snapPoints;
	}
	
	
  private int convertBeatsToSamples() {
  	return (int) (getHostSampleRate()/(getTempoBPM()/60));
  }

	@Override
	public int getLength() {
		return lengthBars * beatsPerBar * convertBeatsToSamples();
	}

	public int getLengthBars() {
		return lengthBars;
	}

	public void setLengthBars(int lengthBars) {
		this.lengthBars = lengthBars;
		updateSnapPoints();
		fireSequenceParametersChanged();
	}

	public int getBeatsPerBar() {
		return beatsPerBar;
	}

	public void setBeatsPerBar(int beatsPerBar) {
		this.beatsPerBar = beatsPerBar;
		updateSnapPoints();
		fireSequenceParametersChanged();
	}

	public int getSnappointsPerBar() {
		return snappointsPerBar;
	}

	public void setSnappointsPerBar(int snappointsPerBar) {
		this.snappointsPerBar = snappointsPerBar;
		updateSnapPoints();
		fireSequenceParametersChanged();
	}

	public float getTempoBPM() {
		if (syncToHostTempo) {
			return getHostTempoBPM();
		}
		
		return tempoBPM;
	}

	public void setTempoBPM(float tempoBPM) {
		this.syncToHostTempo = false;
		this.tempoBPM = tempoBPM;
		updateSnapPoints();
		fireSequenceParametersChanged();
	}

	public boolean isSyncToHostTempo() {
		return syncToHostTempo;
	}

	public void setSyncToHostTempo(boolean syncToHostTempo) {
		this.syncToHostTempo = syncToHostTempo;
		updateSnapPoints();
		fireSequenceParametersChanged();
	}

	
	
	private void fireSequenceParametersChanged() {
		for (SequenceListener l : listeners) {
			l.sequenceParametersChanged();
		}
	}

	public void addSequenceListener(SequenceListener l) {
		listeners.add(l);
	}

	public void removeSequenceListener(SequenceListener l) {
		listeners.remove(l);
	}
	
	
	
	
	
	
	
	
	
	private float getHostSampleRate() {
		if (programManager == null)
			return 44100;
		
		return programManager.getTempoProvider().getSampleRate();
	}

	private float getHostTempoBPM() {
		if (programManager == null)
			return 120;
		
		return programManager.getTempoProvider().getTempo();
	}

	@Override
	public Set<OffsetSampleDataContainer> getSampleData() {
		Set<OffsetSampleDataContainer> retVal = new HashSet<OffsetSampleDataContainer>();

		for (SequencedSequencable ss : sss) {
			Sequencable s = ss.getSequencable();
			for (OffsetSampleDataContainer osdc : s.getSampleData()) {
				retVal.add(new FixedOffsetSampleDataContainer(osdc.getSampleDataContainer(), osdc.getOffset() + ss.getStartOffset()));
			}
		}
		
		return retVal;
	}

	void setProgramManager(ProgramManager programManager) {
		this.programManager = programManager;
		
	}

	
	
}

